nexo-brain 7.30.21 → 7.30.23

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "7.30.21",
3
+ "version": "7.30.23",
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,7 @@
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` is the current packaged-runtime line. Patch release over v7.30.20 - Brain now ships managed default MCP catalog/lock reconciliation and the Operational Closure Plane, with Desktop v0.43.12 consuming the coordinated runtime contract.
21
+ Version `7.30.23` is the current packaged-runtime line. Patch release over v7.30.22 - Brain now ships Opportunity Orchestrator Phase 1 for evidence-backed proactive preparation, read-only dry-run reports, bounded proposal queues, and feedback/suppression controls.
22
22
 
23
23
  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
24
 
@@ -26,6 +26,8 @@ Previously in `7.30.19`: patch release over v7.30.18 - product capabilities now
26
26
 
27
27
  Previously in `7.30.16`: patch release over v7.30.14 - Desktop diagnostics can read embedding migration status without warming models, and the coordinated Desktop update path is covered for bundled model verification and obsolete managed model cleanup.
28
28
 
29
+ Previously in `7.30.15`: patch release over v7.30.14 - Email NEXO closes affirmative actionable replies correctly and avoids reopening processed threads after a sent reply is already recorded.
30
+
29
31
  Previously in `7.30.14`: patch release over v7.30.13 - support tickets, provider capability discovery, task-close rearming, internal audit followups, and the memory-observation watchdog are aligned for Desktop-managed agents.
30
32
 
31
33
  Previously in `7.30.13`: patch release over v7.30.12 - Email NEXO monitor prompts now require a full IMAP body fetch before replying, and managed email defaults use the MXroute `Sent` folder instead of `INBOX.Sent`.
package/bin/nexo-brain.js CHANGED
@@ -1346,7 +1346,7 @@ function getCoreRuntimeFlatFiles(srcDir = path.join(__dirname, "..", "src")) {
1346
1346
  }
1347
1347
 
1348
1348
  function getCoreRuntimePackages() {
1349
- return ["db", "cognitive", "doctor", "local_context", "product_knowledge"];
1349
+ return ["db", "cognitive", "doctor", "local_context", "managed_mcp", "product_knowledge"];
1350
1350
  }
1351
1351
 
1352
1352
  // Brain contracts — files the NEXO Brain publishes to consumers like
@@ -4306,6 +4306,12 @@ async function runSetup() {
4306
4306
  const runtimeCliPath = path.join(NEXO_HOME, "bin", "nexo");
4307
4307
  fs.writeFileSync(runtimeCliPath, runtimeCli);
4308
4308
  fs.chmodSync(runtimeCliPath, 0o755);
4309
+ const managedMcpSource = path.join(__dirname, "nexo-managed-mcp.js");
4310
+ if (fs.existsSync(managedMcpSource)) {
4311
+ const managedMcpTarget = path.join(NEXO_HOME, "bin", "nexo-managed-mcp");
4312
+ fs.copyFileSync(managedMcpSource, managedMcpTarget);
4313
+ fs.chmodSync(managedMcpTarget, 0o755);
4314
+ }
4309
4315
 
4310
4316
  log("Copying core packages...");
4311
4317
  // Core packages (directories with __init__.py)
@@ -30,6 +30,23 @@ function findEntry(capabilityId) {
30
30
  return null;
31
31
  }
32
32
 
33
+ function disabledList() {
34
+ return String(process.env.NEXO_MANAGED_MCP_DISABLED_CAPABILITIES || "")
35
+ .split(",")
36
+ .map((item) => item.trim())
37
+ .filter(Boolean);
38
+ }
39
+
40
+ function isDisabled(capabilityId) {
41
+ const global = String(process.env.NEXO_MANAGED_MCP_DISABLE || "").trim().toLowerCase();
42
+ if (["1", "true", "yes", "on"].includes(global)) return true;
43
+ return disabledList().includes(capabilityId);
44
+ }
45
+
46
+ function readInstalledState() {
47
+ return readJson(path.join(nexoHome(), "runtime", "managed-mcp", "installed-state.json")) || {};
48
+ }
49
+
33
50
  function providerEnv() {
34
51
  return {
35
52
  PATH: process.env.PATH || "",
@@ -38,8 +55,15 @@ function providerEnv() {
38
55
  };
39
56
  }
40
57
 
41
- function stagedProviderCommand(providerId) {
42
- return path.join(nexoHome(), "runtime", "managed-mcp", "artifacts", providerId, "bin", providerId);
58
+ function stagedProviderCommand(providerId, state) {
59
+ const providers = state && state.providers && typeof state.providers === "object" ? state.providers : {};
60
+ const provider = providers[providerId] && typeof providers[providerId] === "object" ? providers[providerId] : {};
61
+ const explicit = process.platform === "win32"
62
+ ? (provider.executable_win32 || provider.executable)
63
+ : provider.executable;
64
+ if (explicit) return String(explicit);
65
+ const suffix = process.platform === "win32" ? ".cmd" : "";
66
+ return path.join(nexoHome(), "runtime", "managed-mcp", "artifacts", providerId, "bin", `${providerId}${suffix}`);
43
67
  }
44
68
 
45
69
  function spawnProvider(command, args, env) {
@@ -63,6 +87,11 @@ function main() {
63
87
  console.error("usage: nexo-managed-mcp run <capability_id>");
64
88
  process.exit(64);
65
89
  }
90
+ if (isDisabled(capabilityId)) {
91
+ console.error(`NEXO managed MCP capability '${capabilityId}' is disabled by policy.`);
92
+ process.exit(78);
93
+ }
94
+ const state = readInstalledState();
66
95
  const entry = findEntry(capabilityId);
67
96
  const meta = entry && entry.nexo ? entry.nexo : {};
68
97
  const providerId = meta.provider_id || "";
@@ -70,12 +99,21 @@ function main() {
70
99
  console.error(`NEXO managed MCP capability '${capabilityId}' is not installed yet.`);
71
100
  process.exit(69);
72
101
  }
73
- const providerCommand = stagedProviderCommand(providerId);
74
- const env = providerEnv();
102
+ const providerCommand = stagedProviderCommand(providerId, state);
103
+ const env = {
104
+ ...providerEnv(),
105
+ NEXO_MANAGED_MCP_CAPABILITY: capabilityId,
106
+ NEXO_MANAGED_MCP_PROVIDER: providerId,
107
+ NEXO_MANAGED_MCP_RISK: String(meta.risk || ""),
108
+ };
75
109
  if (!fs.existsSync(providerCommand)) {
76
110
  const providerPackage = String(meta.provider_package || "");
77
111
  const providerVersion = String(meta.provider_version || "");
78
112
  const providerBin = String(meta.provider_bin || providerId);
113
+ if (String(process.env.NEXO_MANAGED_MCP_ALLOW_NPX_FALLBACK || "").trim() !== "1") {
114
+ console.error(`NEXO managed MCP provider '${providerId}' is not staged. Run nexo_managed_mcp_status(apply=true) to install managed providers.`);
115
+ process.exit(69);
116
+ }
79
117
  if (!providerPackage || !providerVersion || providerVersion === "0.0.0-managed") {
80
118
  console.error(`NEXO managed MCP provider '${providerId}' is not staged and has no exact locked npm package.`);
81
119
  process.exit(69);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "7.30.21",
3
+ "version": "7.30.23",
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",
@@ -4828,6 +4828,7 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
4828
4828
  "cognitive",
4829
4829
  "doctor",
4830
4830
  "local_context",
4831
+ "managed_mcp",
4831
4832
  "product_knowledge",
4832
4833
  "dashboard",
4833
4834
  "rules",
@@ -4970,6 +4971,12 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
4970
4971
  wrapper = bin_dir / "nexo"
4971
4972
  wrapper.write_text(_runtime_cli_wrapper_text())
4972
4973
  wrapper.chmod(0o755)
4974
+ managed_mcp_src = repo_dir / "bin" / "nexo-managed-mcp.js"
4975
+ if managed_mcp_src.is_file():
4976
+ managed_mcp_target = bin_dir / "nexo-managed-mcp"
4977
+ _remove_runtime_copy_target(managed_mcp_target)
4978
+ shutil.copy2(str(managed_mcp_src), str(managed_mcp_target))
4979
+ managed_mcp_target.chmod(0o755)
4973
4980
 
4974
4981
  return {
4975
4982
  "packages": copied_packages,
package/src/cli.py CHANGED
@@ -170,6 +170,48 @@ def _mcp_status(args) -> int:
170
170
  )
171
171
 
172
172
 
173
+ def _closure(args) -> int:
174
+ from closure_plane import closure_next, closure_snapshot, closure_status, refresh_closure_items
175
+ from db import get_db
176
+ from db._schema import run_migrations
177
+
178
+ conn = get_db()
179
+ run_migrations(conn)
180
+ command = str(getattr(args, "closure_command", "") or "status")
181
+ if command == "status":
182
+ payload = closure_status(
183
+ conn,
184
+ refresh=not bool(getattr(args, "no_refresh", False)),
185
+ limit=max(1, min(int(getattr(args, "limit", 10) or 10), 100)),
186
+ )
187
+ elif command == "next":
188
+ if bool(getattr(args, "refresh", False)):
189
+ refresh_closure_items(conn)
190
+ payload = {
191
+ "ok": True,
192
+ "items": closure_next(
193
+ conn,
194
+ limit=max(1, min(int(getattr(args, "limit", 10) or 10), 100)),
195
+ include_waiting=bool(getattr(args, "include_waiting", False)),
196
+ source=str(getattr(args, "source", "") or ""),
197
+ kind=str(getattr(args, "kind", "") or ""),
198
+ state=str(getattr(args, "state", "") or ""),
199
+ max_risk=getattr(args, "max_risk", 0.0),
200
+ area=str(getattr(args, "area", "") or ""),
201
+ ),
202
+ }
203
+ elif command == "snapshot":
204
+ payload = closure_snapshot(
205
+ conn,
206
+ refresh=not bool(getattr(args, "no_refresh", False)),
207
+ snapshot_date=str(getattr(args, "date", "") or ""),
208
+ limit=max(1, min(int(getattr(args, "limit", 10) or 10), 100)),
209
+ )
210
+ else:
211
+ payload = {"ok": False, "error": "unsupported closure command"}
212
+ return _print_json_or_text(payload, as_json=bool(getattr(args, "json", False)))
213
+
214
+
173
215
  def _mcp_write_message(stdin, payload: dict) -> None:
174
216
  raw = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
175
217
  stdin.write(raw + b"\n")
@@ -4307,6 +4349,29 @@ def main():
4307
4349
  help="Install Codex automatically when the selected runtime needs it and it is missing.",
4308
4350
  )
4309
4351
 
4352
+ # -- closure --
4353
+ closure_parser = sub.add_parser("closure", help="Operational closure queue")
4354
+ closure_sub = closure_parser.add_subparsers(dest="closure_command")
4355
+ closure_status_p = closure_sub.add_parser("status", help="Show closure queue status")
4356
+ closure_status_p.add_argument("--limit", type=int, default=10, help="Top items to include")
4357
+ closure_status_p.add_argument("--no-refresh", action="store_true", help="Do not refresh adapters before reading")
4358
+ closure_status_p.add_argument("--json", action="store_true", help="JSON output")
4359
+ closure_next_p = closure_sub.add_parser("next", help="Show ranked closure items")
4360
+ closure_next_p.add_argument("--limit", type=int, default=10, help="Maximum items")
4361
+ closure_next_p.add_argument("--include-waiting", action="store_true", help="Include waiting_user/blocked items")
4362
+ closure_next_p.add_argument("--refresh", action="store_true", help="Refresh adapters before reading")
4363
+ closure_next_p.add_argument("--source", default="", help="Filter by source")
4364
+ closure_next_p.add_argument("--kind", default="", help="Filter by kind")
4365
+ closure_next_p.add_argument("--state", default="", help="Filter by state, e.g. ready or waiting_user")
4366
+ closure_next_p.add_argument("--area", default="", help="Filter by owner/source/kind/payload area")
4367
+ closure_next_p.add_argument("--max-risk", type=float, default=0.0, help="Filter by maximum risk_score")
4368
+ closure_next_p.add_argument("--json", action="store_true", help="JSON output")
4369
+ closure_snapshot_p = closure_sub.add_parser("snapshot", help="Write and show the daily closure snapshot")
4370
+ closure_snapshot_p.add_argument("--date", default="", help="Snapshot date YYYY-MM-DD")
4371
+ closure_snapshot_p.add_argument("--limit", type=int, default=10, help="Top items to include")
4372
+ closure_snapshot_p.add_argument("--no-refresh", action="store_true", help="Do not refresh adapters before snapshot")
4373
+ closure_snapshot_p.add_argument("--json", action="store_true", help="JSON output")
4374
+
4310
4375
  # -- preferences --
4311
4376
  preferences_parser = sub.add_parser(
4312
4377
  "preferences",
@@ -4848,6 +4913,10 @@ def main():
4848
4913
  return _clients_sync(args)
4849
4914
  clients_parser.print_help()
4850
4915
  return 0
4916
+ elif args.command == "closure":
4917
+ if not args.closure_command:
4918
+ args.closure_command = "status"
4919
+ return _closure(args)
4851
4920
  elif args.command == "mcp":
4852
4921
  if args.mcp_command == "status":
4853
4922
  return _mcp_status(args)