nexo-brain 2.6.17 → 2.6.20

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.
@@ -54,18 +54,162 @@ def _codex_bootstrap_config_status() -> dict:
54
54
  "error": str(exc),
55
55
  }
56
56
  managed = bool(payload.get("nexo", {}).get("codex", {}).get("bootstrap_managed"))
57
+ mcp_managed = bool(payload.get("nexo", {}).get("codex", {}).get("mcp_managed"))
57
58
  initial_messages = payload.get("initial_messages", [])
58
59
  has_initial_messages = bool(initial_messages)
60
+ mcp_server = payload.get("mcp_servers", {}).get("nexo", {})
59
61
  return {
60
62
  "exists": True,
61
63
  "path": str(path),
62
64
  "bootstrap_managed": managed,
65
+ "mcp_managed": mcp_managed,
63
66
  "has_initial_messages": has_initial_messages,
64
67
  "model": str(payload.get("model", "") or ""),
65
68
  "reasoning_effort": str(payload.get("model_reasoning_effort", "") or ""),
69
+ "has_mcp_server": isinstance(mcp_server, dict) and bool(mcp_server.get("command")) and bool(mcp_server.get("args")),
70
+ "mcp_runtime_home": str((mcp_server.get("env") or {}).get("NEXO_HOME", "") or ""),
71
+ "mcp_runtime_root": str((mcp_server.get("env") or {}).get("NEXO_CODE", "") or ""),
66
72
  }
67
73
 
68
74
 
75
+ def _claude_desktop_shared_brain_status() -> dict:
76
+ path = Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
77
+ if not path.is_file():
78
+ return {"exists": False, "path": str(path), "shared_brain_managed": False}
79
+ try:
80
+ payload = json.loads(path.read_text())
81
+ except Exception as exc:
82
+ return {
83
+ "exists": True,
84
+ "path": str(path),
85
+ "shared_brain_managed": False,
86
+ "error": str(exc),
87
+ }
88
+ mcp_server = (payload.get("mcpServers") or {}).get("nexo", {})
89
+ metadata = ((payload.get("nexo") or {}).get("claude_desktop") or {})
90
+ return {
91
+ "exists": True,
92
+ "path": str(path),
93
+ "has_mcp_server": isinstance(mcp_server, dict) and bool(mcp_server.get("command")) and bool(mcp_server.get("args")),
94
+ "shared_brain_managed": bool(metadata.get("shared_brain_managed")),
95
+ "shared_brain_mode": str(metadata.get("shared_brain_mode", "") or ""),
96
+ "managed_runtime_home": str(metadata.get("managed_runtime_home", "") or ""),
97
+ "managed_runtime_root": str(metadata.get("managed_runtime_root", "") or ""),
98
+ }
99
+
100
+
101
+ def _recent_codex_session_parity_status(*, days: int = 7, max_files: int = 24) -> dict:
102
+ roots = [
103
+ Path.home() / ".codex" / "sessions",
104
+ Path.home() / ".codex" / "archived_sessions",
105
+ ]
106
+ cutoff = time.time() - (days * 86400)
107
+ candidates: list[tuple[float, Path]] = []
108
+ for root in roots:
109
+ if not root.exists():
110
+ continue
111
+ for path in root.rglob("*.jsonl"):
112
+ try:
113
+ mtime = path.stat().st_mtime
114
+ except OSError:
115
+ continue
116
+ if mtime >= cutoff:
117
+ candidates.append((mtime, path))
118
+ candidates.sort(key=lambda item: item[0], reverse=True)
119
+ files = [path for _, path in candidates[:max_files]]
120
+
121
+ status = {
122
+ "files": len(files),
123
+ "bootstrap_sessions": 0,
124
+ "startup_sessions": 0,
125
+ "heartbeat_sessions": 0,
126
+ "origins": set(),
127
+ "samples": [],
128
+ }
129
+ for path in files:
130
+ saw_bootstrap = False
131
+ saw_startup = False
132
+ saw_heartbeat = False
133
+ origin = ""
134
+ try:
135
+ with path.open() as fh:
136
+ for raw in fh:
137
+ if (
138
+ not saw_bootstrap
139
+ and (
140
+ "NEXO Shared Brain for Codex" in raw
141
+ or "<!-- nexo-codex-agents-version:" in raw
142
+ or "You are NEXO" in raw
143
+ )
144
+ ):
145
+ saw_bootstrap = True
146
+ try:
147
+ event = json.loads(raw)
148
+ except Exception:
149
+ continue
150
+ payload = event.get("payload", {})
151
+ if event.get("type") == "session_meta" and isinstance(payload, dict):
152
+ origin = str(payload.get("originator", "") or payload.get("source", "") or "")
153
+ if event.get("type") != "response_item" or not isinstance(payload, dict):
154
+ continue
155
+ if payload.get("type") != "function_call":
156
+ continue
157
+ name = str(payload.get("name", "") or "")
158
+ if name in {"mcp__nexo__nexo_startup", "nexo_startup"}:
159
+ saw_startup = True
160
+ elif name in {"mcp__nexo__nexo_heartbeat", "nexo_heartbeat"}:
161
+ saw_heartbeat = True
162
+ if saw_bootstrap and saw_startup and saw_heartbeat and origin:
163
+ break
164
+ except Exception:
165
+ continue
166
+ if origin:
167
+ status["origins"].add(origin)
168
+ if saw_bootstrap:
169
+ status["bootstrap_sessions"] += 1
170
+ if saw_startup:
171
+ status["startup_sessions"] += 1
172
+ if saw_heartbeat:
173
+ status["heartbeat_sessions"] += 1
174
+ status["samples"].append(
175
+ {
176
+ "file": str(path),
177
+ "bootstrap": saw_bootstrap,
178
+ "startup": saw_startup,
179
+ "heartbeat": saw_heartbeat,
180
+ "origin": origin,
181
+ }
182
+ )
183
+ status["origins"] = sorted(status["origins"])
184
+ return status
185
+
186
+
187
+ def _client_assumption_regressions() -> list[str]:
188
+ src_root = NEXO_CODE / "src"
189
+ if not src_root.is_dir():
190
+ return []
191
+ allowed_claude_projects = {
192
+ (src_root / "scripts" / "deep-sleep" / "collect.py").resolve(),
193
+ }
194
+ offenders: list[str] = []
195
+ for path in src_root.rglob("*.py"):
196
+ try:
197
+ text = path.read_text()
198
+ except Exception:
199
+ continue
200
+ resolved = path.resolve()
201
+ if ".claude/projects" in text and resolved not in allowed_claude_projects:
202
+ offenders.append(f"{path.relative_to(NEXO_CODE)} hardcodes ~/.claude/projects")
203
+ collect_path = src_root / "scripts" / "deep-sleep" / "collect.py"
204
+ try:
205
+ collect_text = collect_path.read_text()
206
+ except Exception:
207
+ collect_text = ""
208
+ if collect_text and (".claude/projects" in collect_text) and (".codex" not in collect_text or "find_codex_session_files" not in collect_text):
209
+ offenders.append("deep-sleep/collect.py references Claude transcripts without Codex transcript parity")
210
+ return offenders
211
+
212
+
69
213
  def _file_age_seconds(path: Path) -> float | None:
70
214
  """Return file age in seconds, or None if not found."""
71
215
  try:
@@ -1093,6 +1237,11 @@ def check_client_bootstrap_parity(fix: bool = False) -> DoctorCheck:
1093
1237
  severity = "warn"
1094
1238
  evidence.append(f"codex config missing managed bootstrap injection at {codex_config.get('path')}")
1095
1239
  repair_plan.append("Run `nexo clients sync` or `nexo update` so plain Codex sessions inherit the NEXO bootstrap")
1240
+ elif codex_config.get("exists") and not codex_config.get("has_mcp_server"):
1241
+ status = "degraded"
1242
+ severity = "warn"
1243
+ evidence.append(f"codex config missing managed `mcp_servers.nexo` at {codex_config.get('path')}")
1244
+ repair_plan.append("Re-sync Codex so manual sessions keep the shared brain even if `codex mcp add` state drifts")
1096
1245
  elif codex_config.get("exists"):
1097
1246
  evidence.append(
1098
1247
  "codex config bootstrap managed"
@@ -1140,6 +1289,151 @@ def check_client_bootstrap_parity(fix: bool = False) -> DoctorCheck:
1140
1289
  )
1141
1290
 
1142
1291
 
1292
+ def check_codex_session_parity() -> DoctorCheck:
1293
+ try:
1294
+ schedule = _load_json(SCHEDULE_FILE) if SCHEDULE_FILE.is_file() else {}
1295
+ except Exception:
1296
+ schedule = {}
1297
+ prefs = normalize_client_preferences(schedule)
1298
+ wants_codex = bool(
1299
+ prefs.get("interactive_clients", {}).get("codex")
1300
+ or prefs.get("default_terminal_client") == "codex"
1301
+ or (prefs.get("automation_enabled", True) and prefs.get("automation_backend") == "codex")
1302
+ )
1303
+ if not wants_codex:
1304
+ return DoctorCheck(
1305
+ id="runtime.codex_sessions",
1306
+ tier="runtime",
1307
+ status="healthy",
1308
+ severity="info",
1309
+ summary="Codex session parity check skipped (Codex not selected)",
1310
+ )
1311
+
1312
+ audit = _recent_codex_session_parity_status()
1313
+ if audit["files"] == 0:
1314
+ return DoctorCheck(
1315
+ id="runtime.codex_sessions",
1316
+ tier="runtime",
1317
+ status="degraded",
1318
+ severity="warn",
1319
+ summary="No recent Codex sessions found to verify startup discipline",
1320
+ repair_plan=[
1321
+ "Start Codex through `nexo chat` at least once so doctor can verify recent NEXO startup behavior",
1322
+ ],
1323
+ escalation_prompt=(
1324
+ "Codex is selected, but there are no recent durable Codex sessions to inspect. "
1325
+ "NEXO cannot prove that manual Codex sessions are entering the shared-brain startup flow."
1326
+ ),
1327
+ )
1328
+
1329
+ evidence = [
1330
+ f"recent codex sessions inspected: {audit['files']}",
1331
+ f"bootstrap markers seen in {audit['bootstrap_sessions']}/{audit['files']}",
1332
+ f"nexo_startup seen in {audit['startup_sessions']}/{audit['files']}",
1333
+ f"nexo_heartbeat seen in {audit['heartbeat_sessions']}/{audit['files']}",
1334
+ ]
1335
+ if audit["origins"]:
1336
+ evidence.append(f"origins: {', '.join(audit['origins'])}")
1337
+
1338
+ status = "healthy"
1339
+ severity = "info"
1340
+ repair_plan: list[str] = []
1341
+ if audit["bootstrap_sessions"] == 0:
1342
+ status = "degraded"
1343
+ severity = "warn"
1344
+ repair_plan.append("Run `nexo update` or `nexo clients sync` so plain Codex sessions inherit the managed bootstrap")
1345
+ if audit["startup_sessions"] == 0:
1346
+ status = "degraded"
1347
+ severity = "warn"
1348
+ repair_plan.append("Use `nexo chat` or keep the global Codex bootstrap intact so sessions actually call `nexo_startup`")
1349
+
1350
+ return DoctorCheck(
1351
+ id="runtime.codex_sessions",
1352
+ tier="runtime",
1353
+ status=status,
1354
+ severity=severity,
1355
+ summary="Recent Codex sessions show NEXO startup discipline" if status == "healthy" else "Recent Codex sessions need stronger NEXO startup discipline",
1356
+ evidence=evidence,
1357
+ repair_plan=repair_plan,
1358
+ escalation_prompt=(
1359
+ "Codex is selected, but recent durable Codex sessions are not consistently showing NEXO bootstrap markers or `nexo_startup`. "
1360
+ "Manual Codex sessions may still be starting too plain."
1361
+ ) if status != "healthy" else "",
1362
+ )
1363
+
1364
+
1365
+ def check_claude_desktop_shared_brain() -> DoctorCheck:
1366
+ try:
1367
+ schedule = _load_json(SCHEDULE_FILE) if SCHEDULE_FILE.is_file() else {}
1368
+ except Exception:
1369
+ schedule = {}
1370
+ prefs = normalize_client_preferences(schedule)
1371
+ wants_desktop = bool(prefs.get("interactive_clients", {}).get("claude_desktop"))
1372
+ installed = detect_installed_clients().get("claude_desktop", {}).get("installed", False)
1373
+ status_info = _claude_desktop_shared_brain_status()
1374
+
1375
+ if not wants_desktop and not installed:
1376
+ return DoctorCheck(
1377
+ id="runtime.claude_desktop",
1378
+ tier="runtime",
1379
+ status="healthy",
1380
+ severity="info",
1381
+ summary="Claude Desktop shared-brain check skipped (client not installed)",
1382
+ )
1383
+
1384
+ evidence = [
1385
+ f"config: {status_info.get('path')}",
1386
+ f"shared brain mode: {status_info.get('shared_brain_mode') or 'mcp_only'}",
1387
+ ]
1388
+ if status_info.get("managed_runtime_home"):
1389
+ evidence.append(f"runtime home: {status_info.get('managed_runtime_home')}")
1390
+
1391
+ if status_info.get("error"):
1392
+ return DoctorCheck(
1393
+ id="runtime.claude_desktop",
1394
+ tier="runtime",
1395
+ status="degraded",
1396
+ severity="warn",
1397
+ summary="Claude Desktop config is unreadable",
1398
+ evidence=evidence + [status_info["error"]],
1399
+ repair_plan=["Repair Claude Desktop config JSON and re-run `nexo clients sync`"],
1400
+ )
1401
+
1402
+ if not status_info.get("exists") or not status_info.get("has_mcp_server"):
1403
+ return DoctorCheck(
1404
+ id="runtime.claude_desktop",
1405
+ tier="runtime",
1406
+ status="degraded",
1407
+ severity="warn",
1408
+ summary="Claude Desktop is not pointed at the shared NEXO brain",
1409
+ evidence=evidence,
1410
+ repair_plan=["Run `nexo clients sync` so Claude Desktop shares the same local brain"],
1411
+ escalation_prompt=(
1412
+ "Claude Desktop is installed or enabled, but its MCP config does not show the shared `nexo` runtime."
1413
+ ),
1414
+ )
1415
+
1416
+ if not status_info.get("shared_brain_managed"):
1417
+ return DoctorCheck(
1418
+ id="runtime.claude_desktop",
1419
+ tier="runtime",
1420
+ status="degraded",
1421
+ severity="warn",
1422
+ summary="Claude Desktop shares NEXO, but managed metadata is missing",
1423
+ evidence=evidence,
1424
+ repair_plan=["Re-sync Claude Desktop so doctor can verify the managed shared-brain contract"],
1425
+ )
1426
+
1427
+ return DoctorCheck(
1428
+ id="runtime.claude_desktop",
1429
+ tier="runtime",
1430
+ status="healthy",
1431
+ severity="info",
1432
+ summary="Claude Desktop shared-brain parity OK (MCP-only mode)",
1433
+ evidence=evidence,
1434
+ )
1435
+
1436
+
1143
1437
  def check_transcript_source_parity() -> DoctorCheck:
1144
1438
  """Check whether Deep Sleep can see transcript sources for the selected clients."""
1145
1439
  try:
@@ -1207,6 +1501,34 @@ def check_transcript_source_parity() -> DoctorCheck:
1207
1501
  )
1208
1502
 
1209
1503
 
1504
+ def check_client_assumption_regressions() -> DoctorCheck:
1505
+ offenders = _client_assumption_regressions()
1506
+ if not offenders:
1507
+ return DoctorCheck(
1508
+ id="runtime.client_assumptions",
1509
+ tier="runtime",
1510
+ status="healthy",
1511
+ severity="info",
1512
+ summary="No new Claude-only runtime path assumptions detected",
1513
+ )
1514
+ return DoctorCheck(
1515
+ id="runtime.client_assumptions",
1516
+ tier="runtime",
1517
+ status="critical",
1518
+ severity="error",
1519
+ summary=f"Detected {len(offenders)} client-parity regression(s) in runtime source",
1520
+ evidence=offenders[:10],
1521
+ repair_plan=[
1522
+ "Replace Claude-only transcript or hook assumptions with shared client abstractions",
1523
+ "Keep Deep Sleep and startup flows aware of both Claude Code and Codex surfaces",
1524
+ ],
1525
+ escalation_prompt=(
1526
+ "A runtime source file drifted back to a Claude-only assumption. "
1527
+ "Audit the offending file and restore client-agnostic parity before shipping."
1528
+ ),
1529
+ )
1530
+
1531
+
1210
1532
  def run_runtime_checks(fix: bool = False) -> list[DoctorCheck]:
1211
1533
  """Run all runtime-tier checks. Read-only by default."""
1212
1534
  return [
@@ -1216,7 +1538,10 @@ def run_runtime_checks(fix: bool = False) -> list[DoctorCheck]:
1216
1538
  check_cron_freshness(),
1217
1539
  check_client_backend_preferences(),
1218
1540
  check_client_bootstrap_parity(fix=fix),
1541
+ check_codex_session_parity(),
1542
+ check_claude_desktop_shared_brain(),
1219
1543
  check_transcript_source_parity(),
1544
+ check_client_assumption_regressions(),
1220
1545
  check_launchagent_integrity(fix=fix),
1221
1546
  check_personal_script_registry(fix=fix),
1222
1547
  check_skill_health(fix=fix),
@@ -65,6 +65,10 @@ def handle_cognitive_retrieve(
65
65
  mode_parts.append(f"spreading={spreading_depth}")
66
66
  elif spreading_depth is None:
67
67
  mode_parts.append("spreading=AUTO")
68
+ if results:
69
+ top_score = float(results[0].get("score", 0.0) or 0.0)
70
+ confidence = "high" if top_score >= 0.82 else "medium" if top_score >= 0.66 else "low"
71
+ mode_parts.append(f"top_confidence={confidence}")
68
72
  header = f"COGNITIVE RETRIEVE — query: '{query}' | {len(results)} results ({', '.join(mode_parts)})\n\n"
69
73
  return header + formatted
70
74
 
@@ -65,6 +65,10 @@ MACOS_FDA_PROBE_PATHS = (
65
65
  Path.home() / "Library" / "Safari",
66
66
  Path.home() / "Library" / "Application Support" / "AddressBook",
67
67
  )
68
+ DEFAULT_CLAUDE_CODE_MODEL = "claude-opus-4-6[1m]"
69
+ DEFAULT_CLAUDE_CODE_REASONING_EFFORT = ""
70
+ DEFAULT_CODEX_MODEL = "gpt-5.4"
71
+ DEFAULT_CODEX_REASONING_EFFORT = "xhigh"
68
72
 
69
73
 
70
74
  def _schedule_defaults() -> dict:
@@ -81,12 +85,12 @@ def _schedule_defaults() -> dict:
81
85
  "automation_backend": "claude_code",
82
86
  "client_runtime_profiles": {
83
87
  "claude_code": {
84
- "model": "opus",
85
- "reasoning_effort": "",
88
+ "model": DEFAULT_CLAUDE_CODE_MODEL,
89
+ "reasoning_effort": DEFAULT_CLAUDE_CODE_REASONING_EFFORT,
86
90
  },
87
91
  "codex": {
88
- "model": "gpt-5.4",
89
- "reasoning_effort": "xhigh",
92
+ "model": DEFAULT_CODEX_MODEL,
93
+ "reasoning_effort": DEFAULT_CODEX_REASONING_EFFORT,
90
94
  },
91
95
  },
92
96
  "client_install_preferences": {