nexo-brain 7.30.23 → 7.30.25
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/client_sync.py +57 -4
- package/src/doctor/providers/runtime.py +67 -15
- package/src/managed_mcp/__init__.py +8 -1
- package/src/managed_mcp/client_config.py +41 -0
- package/src/scripts/nexo-watchdog.sh +7 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.25",
|
|
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.30.
|
|
21
|
+
Version `7.30.25` is the current packaged-runtime line. Patch release over v7.30.24 - Desktop-facing maintenance diagnostics now self-heal or stay informational when the runtime is healthy, and watchdog uses the canonical cognitive DB layout.
|
|
22
|
+
|
|
23
|
+
Previously in `7.30.24`: patch release over v7.30.23 - client sync now stages and verifies managed MCP providers before writing Brain-owned default MCP entries into Claude, Codex, or Desktop configs.
|
|
24
|
+
|
|
25
|
+
Previously in `7.30.23`: patch release over v7.30.22 - Brain ships Opportunity Orchestrator Phase 1 for evidence-backed proactive preparation, read-only dry-run reports, bounded proposal queues, and feedback/suppression controls.
|
|
22
26
|
|
|
23
27
|
Previously in `7.30.20`: patch release over v7.30.19 - packaged installs now copy the `product_knowledge` package into the installed runtime so `nexo update` can import the new product knowledge tools.
|
|
24
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.25",
|
|
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/client_sync.py
CHANGED
|
@@ -24,11 +24,21 @@ from bootstrap_docs import sync_client_bootstrap
|
|
|
24
24
|
from runtime_home import resolve_nexo_home
|
|
25
25
|
|
|
26
26
|
try:
|
|
27
|
-
from managed_mcp import
|
|
27
|
+
from managed_mcp import (
|
|
28
|
+
build_managed_server_entries,
|
|
29
|
+
merge_json_mcp_servers,
|
|
30
|
+
merge_toml_mcp_servers,
|
|
31
|
+
reconcile_managed_mcp,
|
|
32
|
+
remove_json_nexo_managed_mcp_servers,
|
|
33
|
+
remove_toml_nexo_managed_mcp_servers,
|
|
34
|
+
)
|
|
28
35
|
except Exception:
|
|
29
36
|
build_managed_server_entries = None
|
|
30
37
|
merge_json_mcp_servers = None
|
|
31
38
|
merge_toml_mcp_servers = None
|
|
39
|
+
reconcile_managed_mcp = None
|
|
40
|
+
remove_json_nexo_managed_mcp_servers = None
|
|
41
|
+
remove_toml_nexo_managed_mcp_servers = None
|
|
32
42
|
|
|
33
43
|
try:
|
|
34
44
|
from client_preferences import (
|
|
@@ -842,7 +852,7 @@ def build_server_config(
|
|
|
842
852
|
def _managed_mcp_entries_for(client: str, server_config: dict) -> dict:
|
|
843
853
|
if str(os.environ.get("NEXO_MANAGED_MCP_DISABLE", "")).strip().lower() in {"1", "true", "yes", "on"}:
|
|
844
854
|
return {}
|
|
845
|
-
if not build_managed_server_entries:
|
|
855
|
+
if not build_managed_server_entries or not reconcile_managed_mcp:
|
|
846
856
|
return {}
|
|
847
857
|
env = server_config.get("env") if isinstance(server_config.get("env"), dict) else {}
|
|
848
858
|
nexo_home = str(env.get("NEXO_HOME") or "").strip()
|
|
@@ -855,16 +865,51 @@ def _managed_mcp_entries_for(client: str, server_config: dict) -> dict:
|
|
|
855
865
|
or ""
|
|
856
866
|
).strip()
|
|
857
867
|
try:
|
|
858
|
-
|
|
868
|
+
plan = reconcile_managed_mcp(
|
|
869
|
+
nexo_home=nexo_home,
|
|
870
|
+
runtime_root=runtime_root or None,
|
|
871
|
+
clients=[client],
|
|
872
|
+
apply=True,
|
|
873
|
+
platform=target_platform or None,
|
|
874
|
+
)
|
|
875
|
+
providers = plan.get("providers") if isinstance(plan.get("providers"), dict) else {}
|
|
876
|
+
if not plan.get("ok") or not providers:
|
|
877
|
+
return {}
|
|
878
|
+
entries = build_managed_server_entries(
|
|
859
879
|
client=client,
|
|
860
880
|
nexo_home=nexo_home,
|
|
861
881
|
runtime_root=runtime_root or None,
|
|
862
882
|
platform=target_platform or None,
|
|
863
883
|
)
|
|
884
|
+
required_providers = {
|
|
885
|
+
str((entry.get("nexo") or {}).get("provider_id") or "").strip()
|
|
886
|
+
for entry in entries.values()
|
|
887
|
+
if isinstance(entry, dict)
|
|
888
|
+
}
|
|
889
|
+
required_providers.discard("")
|
|
890
|
+
if not required_providers:
|
|
891
|
+
return {}
|
|
892
|
+
for provider_id in required_providers:
|
|
893
|
+
state = providers.get(provider_id) if isinstance(providers.get(provider_id), dict) else {}
|
|
894
|
+
if state.get("status") != "healthy":
|
|
895
|
+
return {}
|
|
896
|
+
return entries
|
|
864
897
|
except Exception:
|
|
865
898
|
return {}
|
|
866
899
|
|
|
867
900
|
|
|
901
|
+
def _remove_unhealthy_json_managed_mcp(payload: dict) -> dict:
|
|
902
|
+
if not remove_json_nexo_managed_mcp_servers:
|
|
903
|
+
return payload
|
|
904
|
+
return remove_json_nexo_managed_mcp_servers(payload)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def _remove_unhealthy_toml_managed_mcp(payload: dict) -> dict:
|
|
908
|
+
if not remove_toml_nexo_managed_mcp_servers:
|
|
909
|
+
return payload
|
|
910
|
+
return remove_toml_nexo_managed_mcp_servers(payload)
|
|
911
|
+
|
|
912
|
+
|
|
868
913
|
def _claude_code_settings_path(home: Path | None = None) -> Path:
|
|
869
914
|
base = home or _user_home()
|
|
870
915
|
return base / ".claude" / "settings.json"
|
|
@@ -1039,6 +1084,10 @@ def _sync_codex_managed_config(
|
|
|
1039
1084
|
payload = merge_toml_mcp_servers(payload, managed_entries)
|
|
1040
1085
|
codex_table = payload.setdefault("nexo", {}).setdefault("codex", {})
|
|
1041
1086
|
codex_table["managed_default_mcp_count"] = len(managed_entries)
|
|
1087
|
+
else:
|
|
1088
|
+
payload = _remove_unhealthy_toml_managed_mcp(payload)
|
|
1089
|
+
codex_table = payload.setdefault("nexo", {}).setdefault("codex", {})
|
|
1090
|
+
codex_table["managed_default_mcp_count"] = 0
|
|
1042
1091
|
|
|
1043
1092
|
# Ensure Codex headless crons (followup-runner, email-monitor, deep-sleep,
|
|
1044
1093
|
# etc.) do not stall on approval prompts. Only set defaults when the user
|
|
@@ -1056,7 +1105,7 @@ def _sync_codex_managed_config(
|
|
|
1056
1105
|
"hooks_enabled": True,
|
|
1057
1106
|
"model": runtime_profile.get("model", ""),
|
|
1058
1107
|
"reasoning_effort": runtime_profile.get("reasoning_effort", "") or "",
|
|
1059
|
-
"managed_default_mcp_count": len(
|
|
1108
|
+
"managed_default_mcp_count": len(managed_entries) if server_config else 0,
|
|
1060
1109
|
}
|
|
1061
1110
|
|
|
1062
1111
|
|
|
@@ -1450,6 +1499,8 @@ def _sync_json_client(path: Path, server_config: dict, label: str, *, managed_me
|
|
|
1450
1499
|
managed_entries = _managed_mcp_entries_for(label, server_config)
|
|
1451
1500
|
if managed_entries and merge_json_mcp_servers:
|
|
1452
1501
|
payload = merge_json_mcp_servers(payload, managed_entries)
|
|
1502
|
+
else:
|
|
1503
|
+
payload = _remove_unhealthy_json_managed_mcp(payload)
|
|
1453
1504
|
_write_json_object(path, payload)
|
|
1454
1505
|
return {
|
|
1455
1506
|
"ok": True,
|
|
@@ -1537,6 +1588,8 @@ def _sync_claude_code_settings(path: Path, server_config: dict) -> dict:
|
|
|
1537
1588
|
managed_entries = _managed_mcp_entries_for("claude_code", server_config)
|
|
1538
1589
|
if managed_entries and merge_json_mcp_servers:
|
|
1539
1590
|
payload = merge_json_mcp_servers(payload, managed_entries)
|
|
1591
|
+
else:
|
|
1592
|
+
payload = _remove_unhealthy_json_managed_mcp(payload)
|
|
1540
1593
|
_ensure_headless_permissions(payload)
|
|
1541
1594
|
_write_json_object(path, payload)
|
|
1542
1595
|
return {
|
|
@@ -50,6 +50,7 @@ WATCHDOG_FRESHNESS = 3600 # 1 hour (runs every 30 min)
|
|
|
50
50
|
RUNNER_HEALTH_FRESHNESS = 43200 # 12 hours (runner-health-check runs every 6h)
|
|
51
51
|
DEFAULT_CRON_THRESHOLD = 7200 # Fallback when manifest data is unavailable
|
|
52
52
|
LIVE_PROTOCOL_SESSION_FRESHNESS = 1800 # 30 minutes
|
|
53
|
+
AUTOMATION_TELEMETRY_USAGE_WARN_COVERAGE = 99.0
|
|
53
54
|
SPECIAL_ENV_NORMALIZE_IDS = {"prevent-sleep", "tcc-approve"}
|
|
54
55
|
OPTIONALS_FILE = paths.config_dir() / "optionals.json"
|
|
55
56
|
SCHEDULE_FILE = paths.config_dir() / "schedule.json"
|
|
@@ -1071,6 +1072,43 @@ def _run_at_load_cron_ids() -> set[str]:
|
|
|
1071
1072
|
return ids
|
|
1072
1073
|
|
|
1073
1074
|
|
|
1075
|
+
def _launchd_calendar_weekdays(schedule: dict) -> list[int]:
|
|
1076
|
+
raw = schedule.get("weekdays") or schedule.get("Weekdays")
|
|
1077
|
+
if raw is None and "weekday" in schedule:
|
|
1078
|
+
raw = [schedule.get("weekday")]
|
|
1079
|
+
if raw is None and "Weekday" in schedule:
|
|
1080
|
+
raw = [schedule.get("Weekday")]
|
|
1081
|
+
if isinstance(raw, str):
|
|
1082
|
+
parts = [part.strip() for part in raw.replace("+", ",").split(",")]
|
|
1083
|
+
elif isinstance(raw, (list, tuple, set)):
|
|
1084
|
+
parts = list(raw)
|
|
1085
|
+
else:
|
|
1086
|
+
return []
|
|
1087
|
+
selected: set[int] = set()
|
|
1088
|
+
for part in parts:
|
|
1089
|
+
try:
|
|
1090
|
+
selected.add(int(part) % 7)
|
|
1091
|
+
except Exception:
|
|
1092
|
+
continue
|
|
1093
|
+
if len(selected) >= 7:
|
|
1094
|
+
return []
|
|
1095
|
+
return [day for day in (1, 2, 3, 4, 5, 6, 0) if day in selected]
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def _launchd_calendar_interval(schedule: dict) -> dict | list[dict]:
|
|
1099
|
+
base = {}
|
|
1100
|
+
if "hour" in schedule:
|
|
1101
|
+
base["Hour"] = schedule["hour"]
|
|
1102
|
+
if "minute" in schedule:
|
|
1103
|
+
base["Minute"] = schedule["minute"]
|
|
1104
|
+
weekdays = _launchd_calendar_weekdays(schedule)
|
|
1105
|
+
if weekdays:
|
|
1106
|
+
if len(weekdays) == 1:
|
|
1107
|
+
return {**base, "Weekday": weekdays[0]}
|
|
1108
|
+
return [{**base, "Weekday": day} for day in weekdays]
|
|
1109
|
+
return base
|
|
1110
|
+
|
|
1111
|
+
|
|
1074
1112
|
def _launchagent_schedule_expectations() -> dict[str, dict]:
|
|
1075
1113
|
expectations = {}
|
|
1076
1114
|
for cron in _enabled_manifest_crons():
|
|
@@ -1096,14 +1134,7 @@ def _launchagent_schedule_expectations() -> dict[str, dict]:
|
|
|
1096
1134
|
expected["schedule_configured"] = True
|
|
1097
1135
|
elif "schedule" in cron:
|
|
1098
1136
|
schedule = resolve_declared_schedule(cron)
|
|
1099
|
-
|
|
1100
|
-
if "hour" in schedule:
|
|
1101
|
-
cal["Hour"] = schedule["hour"]
|
|
1102
|
-
if "minute" in schedule:
|
|
1103
|
-
cal["Minute"] = schedule["minute"]
|
|
1104
|
-
if "weekday" in schedule:
|
|
1105
|
-
cal["Weekday"] = schedule["weekday"]
|
|
1106
|
-
expected["StartCalendarInterval"] = cal
|
|
1137
|
+
expected["StartCalendarInterval"] = _launchd_calendar_interval(schedule)
|
|
1107
1138
|
expected["RunAtLoad"] = True if should_run_at_load(cron) else None
|
|
1108
1139
|
expected["schedule_configured"] = True
|
|
1109
1140
|
elif should_run_at_load(cron):
|
|
@@ -3380,6 +3411,7 @@ def check_release_trace_hygiene() -> DoctorCheck:
|
|
|
3380
3411
|
evidence=[],
|
|
3381
3412
|
repair_plan=[],
|
|
3382
3413
|
escalation_prompt="",
|
|
3414
|
+
category="operator_history",
|
|
3383
3415
|
)
|
|
3384
3416
|
|
|
3385
3417
|
try:
|
|
@@ -3402,6 +3434,7 @@ def check_release_trace_hygiene() -> DoctorCheck:
|
|
|
3402
3434
|
evidence=[],
|
|
3403
3435
|
repair_plan=[],
|
|
3404
3436
|
escalation_prompt="",
|
|
3437
|
+
category="operator_history",
|
|
3405
3438
|
)
|
|
3406
3439
|
|
|
3407
3440
|
stale_run_samples: list[str] = []
|
|
@@ -3465,6 +3498,7 @@ def check_release_trace_hygiene() -> DoctorCheck:
|
|
|
3465
3498
|
evidence=[str(exc)],
|
|
3466
3499
|
repair_plan=["Inspect workflow_goals/workflow_runs state manually"],
|
|
3467
3500
|
escalation_prompt="Release traces could not be audited, so stale audit artifacts may be hiding in the runtime.",
|
|
3501
|
+
category="operator_history",
|
|
3468
3502
|
)
|
|
3469
3503
|
|
|
3470
3504
|
evidence = [
|
|
@@ -3486,6 +3520,7 @@ def check_release_trace_hygiene() -> DoctorCheck:
|
|
|
3486
3520
|
"Keep workflow/goal state aligned with the real shipped state after releases",
|
|
3487
3521
|
],
|
|
3488
3522
|
escalation_prompt="Audit/release traces drifted away from reality, which makes shipping state look ambiguous.",
|
|
3523
|
+
category="operator_history",
|
|
3489
3524
|
)
|
|
3490
3525
|
return DoctorCheck(
|
|
3491
3526
|
id="runtime.release_trace_hygiene",
|
|
@@ -3496,6 +3531,7 @@ def check_release_trace_hygiene() -> DoctorCheck:
|
|
|
3496
3531
|
evidence=evidence,
|
|
3497
3532
|
repair_plan=[],
|
|
3498
3533
|
escalation_prompt="",
|
|
3534
|
+
category="operator_history",
|
|
3499
3535
|
)
|
|
3500
3536
|
|
|
3501
3537
|
|
|
@@ -3754,7 +3790,7 @@ def check_automation_telemetry(days: int = 7) -> DoctorCheck:
|
|
|
3754
3790
|
status = "healthy"
|
|
3755
3791
|
severity = "info"
|
|
3756
3792
|
repair_plan: list[str] = []
|
|
3757
|
-
if usage_coverage <
|
|
3793
|
+
if usage_coverage < AUTOMATION_TELEMETRY_USAGE_WARN_COVERAGE and missing_usage_runs > 1:
|
|
3758
3794
|
status = "degraded"
|
|
3759
3795
|
severity = "warn"
|
|
3760
3796
|
repair_plan.append("Restore backend usage parsing so automation runs always emit token telemetry")
|
|
@@ -3933,10 +3969,21 @@ def check_local_index_hygiene(fix: bool = False) -> DoctorCheck:
|
|
|
3933
3969
|
try:
|
|
3934
3970
|
from local_context import api as local_context_api
|
|
3935
3971
|
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3972
|
+
result = None
|
|
3973
|
+
for attempt in range(3):
|
|
3974
|
+
try:
|
|
3975
|
+
try:
|
|
3976
|
+
result = local_context_api.local_index_hygiene(fix=fix, quick=not fix)
|
|
3977
|
+
except TypeError:
|
|
3978
|
+
result = local_context_api.local_index_hygiene(fix=fix)
|
|
3979
|
+
break
|
|
3980
|
+
except sqlite3.OperationalError as exc:
|
|
3981
|
+
if "locked" not in str(exc).lower() or attempt >= 2:
|
|
3982
|
+
raise
|
|
3983
|
+
time.sleep(0.2 * (attempt + 1))
|
|
3984
|
+
if result is None:
|
|
3985
|
+
raise RuntimeError("local_index_hygiene returned no result")
|
|
3986
|
+
|
|
3940
3987
|
residue = result.get("residue") or {}
|
|
3941
3988
|
cleanup = result.get("cleanup") or {}
|
|
3942
3989
|
privacy = result.get("privacy") or {}
|
|
@@ -3958,13 +4005,18 @@ def check_local_index_hygiene(fix: bool = False) -> DoctorCheck:
|
|
|
3958
4005
|
"privacy_truncated=" + str(privacy_truncated),
|
|
3959
4006
|
]
|
|
3960
4007
|
evidence.extend(f"root={path}" for path in suspect_roots[:5])
|
|
3961
|
-
if residue_total == 0 and privacy_residue_total == 0 and not suspect_roots
|
|
4008
|
+
if residue_total == 0 and privacy_residue_total == 0 and not suspect_roots:
|
|
4009
|
+
summary = (
|
|
4010
|
+
"Local memory index quick scan found no residue"
|
|
4011
|
+
if privacy_truncated
|
|
4012
|
+
else "Local memory index hygiene is clean"
|
|
4013
|
+
)
|
|
3962
4014
|
return DoctorCheck(
|
|
3963
4015
|
id="runtime.local_index_hygiene",
|
|
3964
4016
|
tier="runtime",
|
|
3965
4017
|
status="healthy",
|
|
3966
4018
|
severity="info",
|
|
3967
|
-
summary=
|
|
4019
|
+
summary=summary,
|
|
3968
4020
|
evidence=evidence,
|
|
3969
4021
|
repair_plan=[],
|
|
3970
4022
|
)
|
|
@@ -11,7 +11,12 @@ from .catalog import (
|
|
|
11
11
|
provider_for_capability,
|
|
12
12
|
validate_catalog_lock,
|
|
13
13
|
)
|
|
14
|
-
from .client_config import
|
|
14
|
+
from .client_config import (
|
|
15
|
+
merge_json_mcp_servers,
|
|
16
|
+
merge_toml_mcp_servers,
|
|
17
|
+
remove_json_nexo_managed_mcp_servers,
|
|
18
|
+
remove_toml_nexo_managed_mcp_servers,
|
|
19
|
+
)
|
|
15
20
|
from .reconcile import managed_mcp_status, reconcile_managed_mcp
|
|
16
21
|
|
|
17
22
|
__all__ = [
|
|
@@ -26,6 +31,8 @@ __all__ = [
|
|
|
26
31
|
"validate_catalog_lock",
|
|
27
32
|
"merge_json_mcp_servers",
|
|
28
33
|
"merge_toml_mcp_servers",
|
|
34
|
+
"remove_json_nexo_managed_mcp_servers",
|
|
35
|
+
"remove_toml_nexo_managed_mcp_servers",
|
|
29
36
|
"managed_mcp_status",
|
|
30
37
|
"reconcile_managed_mcp",
|
|
31
38
|
]
|
|
@@ -11,6 +11,47 @@ def _is_nexo_owned(entry: Any) -> bool:
|
|
|
11
11
|
return isinstance(meta, dict) and meta.get("owner") == "nexo"
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def _managed_servers_metadata(container: dict[str, Any]) -> dict[str, Any]:
|
|
15
|
+
nexo_meta = container.get("nexo") if isinstance(container.get("nexo"), dict) else {}
|
|
16
|
+
managed = nexo_meta.get("managed_mcp") if isinstance(nexo_meta.get("managed_mcp"), dict) else {}
|
|
17
|
+
servers = managed.get("servers") if isinstance(managed.get("servers"), dict) else {}
|
|
18
|
+
return servers
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _remove_nexo_owned_by_metadata(servers: dict[str, Any], managed_servers: dict[str, Any]) -> None:
|
|
22
|
+
for name, meta in list(managed_servers.items()):
|
|
23
|
+
if not (isinstance(meta, dict) and meta.get("owner") == "nexo"):
|
|
24
|
+
continue
|
|
25
|
+
current = servers.get(name)
|
|
26
|
+
if current is None or _is_nexo_owned(current) or name in servers:
|
|
27
|
+
servers.pop(name, None)
|
|
28
|
+
managed_servers.pop(name, None)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def remove_json_nexo_managed_mcp_servers(payload: dict[str, Any]) -> dict[str, Any]:
|
|
32
|
+
result = deepcopy(payload) if isinstance(payload, dict) else {}
|
|
33
|
+
servers = result.get("mcpServers")
|
|
34
|
+
if not isinstance(servers, dict):
|
|
35
|
+
return result
|
|
36
|
+
managed_servers = _managed_servers_metadata(result)
|
|
37
|
+
for name, entry in list(servers.items()):
|
|
38
|
+
if _is_nexo_owned(entry):
|
|
39
|
+
servers.pop(name, None)
|
|
40
|
+
managed_servers.pop(name, None)
|
|
41
|
+
_remove_nexo_owned_by_metadata(servers, managed_servers)
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def remove_toml_nexo_managed_mcp_servers(payload: dict[str, Any]) -> dict[str, Any]:
|
|
46
|
+
result = deepcopy(payload) if isinstance(payload, dict) else {}
|
|
47
|
+
servers = result.get("mcp_servers")
|
|
48
|
+
if not isinstance(servers, dict):
|
|
49
|
+
return result
|
|
50
|
+
managed_servers = _managed_servers_metadata(result)
|
|
51
|
+
_remove_nexo_owned_by_metadata(servers, managed_servers)
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
|
|
14
55
|
def merge_json_mcp_servers(payload: dict[str, Any], entries: dict[str, dict[str, Any]]) -> dict[str, Any]:
|
|
15
56
|
result = deepcopy(payload) if isinstance(payload, dict) else {}
|
|
16
57
|
servers = result.setdefault("mcpServers", {})
|
|
@@ -23,6 +23,8 @@ LOG_DIR="$NEXO_HOME/runtime/logs"
|
|
|
23
23
|
[ ! -d "$LOG_DIR" ] && [ -d "$NEXO_HOME/logs" ] && LOG_DIR="$NEXO_HOME/logs"
|
|
24
24
|
DATA_DIR="$NEXO_HOME/runtime/data"
|
|
25
25
|
[ ! -d "$DATA_DIR" ] && [ -d "$NEXO_HOME/data" ] && DATA_DIR="$NEXO_HOME/data"
|
|
26
|
+
COGNITIVE_DIR="$NEXO_HOME/runtime/cognitive"
|
|
27
|
+
[ ! -d "$COGNITIVE_DIR" ] && COGNITIVE_DIR="$DATA_DIR"
|
|
26
28
|
BACKUP_DIR="$NEXO_HOME/runtime/backups"
|
|
27
29
|
[ ! -d "$BACKUP_DIR" ] && [ -d "$NEXO_HOME/backups" ] && BACKUP_DIR="$NEXO_HOME/backups"
|
|
28
30
|
DB_PATH="$DATA_DIR/nexo.db"
|
|
@@ -841,8 +843,7 @@ for monitor in "${MONITORS[@]}"; do
|
|
|
841
843
|
fi
|
|
842
844
|
fi
|
|
843
845
|
elif [ "$age" -gt $(( max_stale * 3 )) ]; then
|
|
844
|
-
|
|
845
|
-
details="${details}In-flight for ${stale_age} (long-running, process alive). "
|
|
846
|
+
details="${details}In-flight for ${stale_age} (long-running, process alive; observing). "
|
|
846
847
|
else
|
|
847
848
|
details="${details}In-flight (started ${stale_age}). "
|
|
848
849
|
fi
|
|
@@ -1206,7 +1207,10 @@ fi
|
|
|
1206
1207
|
# --- Cognitive DB check ---
|
|
1207
1208
|
COG_STATUS="PASS"
|
|
1208
1209
|
COG_DETAIL=""
|
|
1209
|
-
COG_DB="$
|
|
1210
|
+
COG_DB="$COGNITIVE_DIR/cognitive.db"
|
|
1211
|
+
if [ ! -f "$COG_DB" ] && [ -f "$DATA_DIR/cognitive.db" ]; then
|
|
1212
|
+
COG_DB="$DATA_DIR/cognitive.db"
|
|
1213
|
+
fi
|
|
1210
1214
|
if [ -f "$COG_DB" ]; then
|
|
1211
1215
|
COG_INT=$(sqlite3 "$COG_DB" "PRAGMA integrity_check;" 2>/dev/null || echo "CORRUPT")
|
|
1212
1216
|
if [ "$COG_INT" != "ok" ]; then
|